﻿using System;
using System.Text;
using zTalk.Network;

namespace zTalk
{
    public class PacketHandlerServer : PacketHandlerBase, IPacketHandler
    {
        public void Handle(SocketBase client, Packet packet)
        {
            try
            {
                switch (packet.Opcode)
                {
                    case Opcodes.PING:
                        handlePing(client, packet);
                        break;
                    case Opcodes.PONG:
                        handlePong(client, packet);
                        break;
                    case Opcodes.LOGIN:
                        handleLogin(client, packet);
                        break;
                    case Opcodes.REGISTER:
                        handleRegister(client, packet);
                        break;
                    case Opcodes.MESSAGE:
                        handleMessage(client, packet);
                        break;
                    case Opcodes.ADD_FRIEND:
                        handleAddFriend(client, packet);
                        break;
                    case Opcodes.REMOVE_FRIEND:
                        handleRemoveFriend(client, packet);
                        break;
                    case Opcodes.GET_FRIENDS:
                        handleGetFriends(client, packet);
                        break;
                    case Opcodes.USER_CHANGE:
                        handleUserChange(client, packet);
                        break;
                    case Opcodes.UPDATE_PROFILE:
                        handleUpdateProfile(client, packet);
                        break;
                    case Opcodes.GET_PROFILE:
                        handleGetProfile(client, packet);
                        break;
                    case Opcodes.UPDATE_PASSWORD:
                        handleUpdatePassword(client, packet);
                        break;
                    case Opcodes.SAVE_AVATAR:
                        handleSaveAvatar(client, packet);
                        break;
                    case Opcodes.GET_USER:
                        handleGetUser(client, packet);
                        break;
                    case Opcodes.USER_LOGOUT:
                        handleUserLogout(client, packet);
                        break;
                    case Opcodes.GET_MESSAGES:
                        handleGetMessages(client, packet);
                        break;
                    /*case Opcodes.GET_AVATAR:
                        handleGetAvatar(client, packet);
                        break;*/
                    /*case Opcodes.FILE_SEND:
                        handleFileSend(client, packet);
                        break;
                    case Opcodes.FILE_ACCEPT:
                        handleFileAccept(client, packet);
                        break;
                    case Opcodes.FILE_TRANSFER:
                        handleFileTransfer(client, packet);
                        break;*/

                    default:
                        throw new Exception(
                            "Invalid operation code " + packet.Opcode.ToString()
                        );
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private void handlePing(SocketBase client, Packet packet)
        {
            DateTime d = DateTime.Parse(Encoding.UTF8.GetString(packet.Data));
            Packet p = Packet.Create(Opcodes.PONG, d.ToString());
            client.SendPacket(p);
        }

        private void handlePong(SocketBase client, Packet packet)
        {
            DateTime d = DateTime.Parse(Encoding.UTF8.GetString(packet.Data));
            TimeSpan interval = DateTime.Now - d;
            //Console.WriteLine("Ping: {0}", interval.ToString());
        }

        private void handleLogin(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string username = data[0];
            string password = data[1];
            string json = "error";

            try
            {
                User user = UserManager.Login(username, password);
                ((SocketUser)client).Username = user.Username;
                ((SocketUser)client).UserID = user.UserID;

                UserProto proto = new UserProto
                {
                    Username = user.Username,
                    Status = (UserStatus)user.Status,
                    About = user.About,
                    Avatar = user.Avatar,
                };

                json = Json.Encode<UserProto>(proto);
                Packet p = Packet.Create(Opcodes.LOGIN, json);
                client.SendPacket(p);

                // notify
                UserProto[] friends = UserManager.GetNotifyFriends(((SocketUser)client).UserID);

                foreach (var friend in friends)
                {
                    if (SocketUser.Users.ContainsKey(friend.Username))
                    {
                        p = Packet.Create(Opcodes.USER_CHANGE,
                            Json.Encode<UserProto>(proto));
                        SocketUser.Users[friend.Username].SendPacket(p);
                    }
                }
            }
            catch
            {
                Packet p = Packet.Create(Opcodes.LOGIN, json);
                client.SendPacket(p);
            }
        }

        private void handleRegister(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string username = data[0];
            string password = data[1];

            try
            {
                UserManager.Register(username, password);
                sendData(client, Opcodes.REGISTER, new string[] { "ok" });
            }
            catch
            {
                sendData(client, Opcodes.REGISTER, new string[] { "error" });
            }
        }

        private void handleMessage(SocketBase client, Packet packet)
        {
            MessageProto message = Json.Decode<MessageProto>(
                Encoding.UTF8.GetString(packet.Data)
            );
            message.Time = DateTime.Now.ToString("HH:mm:ss");

            if (SocketUser.Users.ContainsKey(message.Destination))
            {
                SocketUser user = SocketUser.Users[message.Destination];
                //MessageProto reply = message;
                Packet p = Packet.Create(Opcodes.MESSAGE, Json.Encode<MessageProto>(message));
                user.SendPacket(p);
            }
            else
            {
                UserManager.AddMessage(message);
                sendData(client, Opcodes.USER_OFFLINE, new string[] { message.Destination });
            }
        }

        private void handleAddFriend(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string friend = data[0];
            string json = "error";

            try
            {
                User u = UserManager.AddFriend(((SocketUser)client).UserID, friend);
                UserProto proto = new UserProto
                {
                    Username = u.Username,
                    Status = (UserStatus)u.Status,
                    About = u.About,
                    Avatar = u.Avatar,
                };

                json = Json.Encode<UserProto>(proto);
            }
            catch (Exception) { }

            Packet p = Packet.Create(Opcodes.ADD_FRIEND, json);
            client.SendPacket(p);
        }

        private void handleRemoveFriend(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string friend = data[0];
            string json = "error";

            try
            {
                UserManager.RemoveFriend(((SocketUser)client).UserID, friend);
                json = "ok";
            }
            catch (Exception) { }

            Packet p = Packet.Create(Opcodes.REMOVE_FRIEND, json);
            client.SendPacket(p);
        }

        private void handleGetFriends(SocketBase client, Packet packet)
        {
            UserProto[] users = UserManager.GetFriends(((SocketUser)client).UserID);
            Packet p = Packet.Create(Opcodes.GET_FRIENDS, Json.Encode<UserProto[]>(users));

            client.SendPacket(p);
        }

        private void handleUserChange(SocketBase client, Packet packet)
        {
            UserProto changes = Json.Decode<UserProto>(
                Encoding.UTF8.GetString(packet.Data)
            );

            UserManager.ChangeUser(((SocketUser)client).UserID, changes);
            changes.Username = ((SocketUser)client).Username;

            UserProto[] friends = UserManager.GetNotifyFriends(((SocketUser)client).UserID);

            foreach (var friend in friends)
            {
                if (SocketUser.Users.ContainsKey(friend.Username))
                {
                    Packet p = Packet.Create(Opcodes.USER_CHANGE, 
                        Json.Encode<UserProto>(changes));
                    SocketUser.Users[friend.Username].SendPacket(p);
                }
            }
        }

        private void handleUpdateProfile(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string about = data[0];
            string name = data[1];
            string email = data[2];
            int gender = int.Parse(data[3]);

            try
            {
                UserManager.UpdateProfile(((SocketUser)client).UserID,
                    about, name, email, gender);
                sendData(client, Opcodes.UPDATE_PROFILE, new string[] { "ok" });
            }
            catch
            {
                sendData(client, Opcodes.UPDATE_PROFILE, new string[] { "error" });
            }
        }

        private void handleGetProfile(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string username = data[0];

            if ((username == ((SocketUser)client).Username) || 
                (UserManager.IsFriend(((SocketUser)client).UserID, username)))
            {
                string[] res = UserManager.GetProfile(username);

                sendData(client, Opcodes.GET_PROFILE, res);
            }
            else
            {
                sendData(client, Opcodes.GET_PROFILE, new string[] { "error" });
            }
        }

        private void handleUpdatePassword(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string oldpass = data[0];
            string newpass = data[1];

            try
            {
                UserManager.UpdatePassword(((SocketUser)client).UserID, oldpass, newpass);
                sendData(client, Opcodes.UPDATE_PASSWORD, new string[] { "ok" });
            }
            catch
            {
                sendData(client, Opcodes.UPDATE_PASSWORD, new string[] { "error" });
            }
        }

        private void handleSaveAvatar(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string avatar64 = data[0];

            try
            {
                UserManager.SaveAvatar(((SocketUser)client).UserID, avatar64);
                sendData(client, Opcodes.SAVE_AVATAR, new string[] { "ok" });
            }
            catch
            {
                sendData(client, Opcodes.SAVE_AVATAR, new string[] { "error" });
            }
        }

        /*private void handleGetAvatar(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string username = data[0];

            try
            {
                string avatar64 = UserManager.GetAvatar(username);
                sendData(client, Opcodes.GET_AVATAR, new string[] { username, avatar64 });
            }
            catch
            {
                sendData(client, Opcodes.GET_AVATAR, new string[] { username, "" });
            }
        }*/

        /*private void handleFileSend(SocketBase client, Packet packet)
        {
            FileProto message = Json.Decode<FileProto>(
                Encoding.UTF8.GetString(packet.Data)
            );

            if (SocketUser.Users.ContainsKey(message.Destination))
            {
                SocketUser user = SocketUser.Users[message.Destination];
                Packet p = Packet.Create(Opcodes.FILE_SEND, Json.Encode<FileProto>(message));
                user.SendPacket(p);
            }
            else
            {
                sendData(client, Opcodes.USER_OFFLINE, new string[] { message.Destination });
            }
        }

        private void handleFileAccept(SocketBase client, Packet packet)
        {
            FileProto message = Json.Decode<FileProto>(
                Encoding.UTF8.GetString(packet.Data)
            );

            if (SocketUser.Users.ContainsKey(message.Destination))
            {
                SocketUser user = SocketUser.Users[message.Destination];
                Packet p = Packet.Create(Opcodes.FILE_ACCEPT, Json.Encode<FileProto>(message));
                user.SendPacket(p);
            }
            else
            {
                sendData(client, Opcodes.USER_OFFLINE, new string[] { message.Destination });
            }
        }

        private void handleFileTransfer(SocketBase client, Packet packet)
        {
            MessageProto message = Json.Decode<MessageProto>(
                Encoding.UTF8.GetString(packet.Data)
            );

            if (SocketUser.Users.ContainsKey(message.Destination))
            {
                SocketUser user = SocketUser.Users[message.Destination];
                Packet p = Packet.Create(Opcodes.FILE_TRANSFER, Json.Encode<MessageProto>(message));
                user.SendPacket(p);
            }
            else
            {
                sendData(client, Opcodes.USER_OFFLINE, new string[] { message.Destination });
            }
        }*/

        private void handleGetUser(SocketBase client, Packet packet)
        {
            string[] data = getData(packet);
            string username = data[0];

            try
            {
                UserProto proto = UserManager.GetUser(username);
                Packet p = Packet.Create(Opcodes.GET_USER, Json.Encode<UserProto>(proto));
                client.SendPacket(p);
            }
            catch
            {
                sendData(client, Opcodes.GET_USER, new string[] { "error" });
            }
        }

        private void handleUserLogout(SocketBase client, Packet packet)
        {
            UserManager.Logout(((SocketUser)client).Username);

            UserProto[] friends = UserManager.GetNotifyFriends(((SocketUser)client).UserID);

            foreach (var friend in friends)
            {
                if (SocketUser.Users.ContainsKey(friend.Username))
                {
                    Packet p = Packet.Create(Opcodes.USER_LOGOUT,
                        Json.Encode<string[]>(new string[] { ((SocketUser)client).Username }));
                    SocketUser.Users[friend.Username].SendPacket(p);
                }
            }
        }

        private void handleGetMessages(SocketBase client, Packet packet)
        {
            // send offline messages
            MessageProto[] messages = UserManager.GetMessages(((SocketUser)client).Username);

            foreach (var message in messages)
            {
                Packet p = Packet.Create(Opcodes.MESSAGE, Json.Encode<MessageProto>(message));
                client.SendPacket(p);
            }
        }
    }
}
